﻿namespace Cos.Core.NHibernate
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Cos.Core.Attributes;

    using global::NHibernate;
    using global::NHibernate.Context;

    public class NHibernateSessionManager : ISessionManager
    {
        private readonly ISessionFactory sessionFactory;

        private Stack<Tuple<ITransactionAttribute, ISession>> sessionStack;

        public NHibernateSessionManager(ISessionFactory sessionFactory)
        {
            this.sessionFactory = sessionFactory;
            this.sessionStack = new Stack<Tuple<ITransactionAttribute, ISession>>();
        }

        public void Execute(ITransactionAttribute transactionAttribute)
        {
            if (this.sessionStack.Count == 0)
            {
                var session = this.sessionFactory.OpenSession();
                this.sessionStack.Push(new Tuple<ITransactionAttribute, ISession>(transactionAttribute, session));
                CurrentSessionContext.Bind(session);
            }

            var currentSession = this.sessionFactory.GetCurrentSession();

            switch (transactionAttribute.Propagation)
            {
                case Propagation.Required:
                    if (!currentSession.Transaction.IsActive && !currentSession.Transaction.WasCommitted && !currentSession.Transaction.WasRolledBack)
                    {
                        currentSession.Transaction.Begin(transactionAttribute.IsolationLevel);
                    }

                    break;
                case Propagation.Supports:
                    break;
                case Propagation.Mandatory:
                    if (!currentSession.Transaction.IsActive)
                    {
                        throw new TransactionException("Transaction is mandatory. No transaction is active");
                    }

                    break;
                case Propagation.RequiresNew:
                    if (!currentSession.Transaction.IsActive)
                    {
                        currentSession.Transaction.Begin(transactionAttribute.IsolationLevel);
                    }
                    else
                    {
                        var session = this.sessionFactory.OpenSession();
                        session.Transaction.Begin(transactionAttribute.IsolationLevel);
                        this.sessionStack.Push(new Tuple<ITransactionAttribute, ISession>(transactionAttribute, session));
                        CurrentSessionContext.Bind(session);
                    }

                    break;
                case Propagation.NotSupported:
                    if (currentSession.Transaction.IsActive)
                    {
                        var session = this.sessionFactory.OpenSession();
                        this.sessionStack.Push(new Tuple<ITransactionAttribute, ISession>(transactionAttribute, session));
                        CurrentSessionContext.Bind(session);
                    }

                    break;
                case Propagation.Never:
                    if (currentSession.Transaction.IsActive)
                    {
                        throw new TransactionException("Transaction is not supported. Transaction is active");
                    }

                    break;
                case Propagation.Nested:
                    throw new NotImplementedException("Nested propagation is not implemented");
                default: throw new InvalidOperationException("Invalid propagation");
            }
        }

        public void Executed(ITransactionAttribute transactionAttribute)
        {
            if (this.sessionStack.Count > 0 && transactionAttribute == this.sessionStack.Peek().Item1)
            {
                var session = this.sessionStack.Pop().Item2;

                if (transactionAttribute.ReadOnly && session.Transaction.IsActive && !session.Transaction.WasCommitted && !session.Transaction.WasRolledBack)
                {
                    session.Transaction.Rollback();
                }
                else if (session.Transaction.IsActive && !session.Transaction.WasCommitted && !session.Transaction.WasRolledBack)
                {
                    session.Transaction.Commit();
                }

                if (this.sessionStack.Count > 0)
                {
                    CurrentSessionContext.Bind(this.sessionStack.Peek().Item2);
                }
            }
        }

        public void Error(ITransactionAttribute transactionAttribute, Exception exception)
        {
            var currentSession = this.sessionFactory.GetCurrentSession();

            if (!currentSession.Transaction.IsActive)
            {
                return;
            }

            if (transactionAttribute.RollbackFor != null && transactionAttribute.RollbackFor.Any(e => e.FullName == exception.GetType().FullName))
            {
                currentSession.Transaction.Rollback();
            }
            else if (transactionAttribute.NoRollbackFor != null && transactionAttribute.NoRollbackFor.Any(e => e.FullName == exception.GetType().FullName))
            {
                // Do nothing
            }
            else
            {
                currentSession.Transaction.Rollback();
            }
        }
    }
}
